package ar.edu.uba.fi.jmit.aspecter;

import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.expr.Expr;
import ar.edu.uba.fi.jmit.aspecter.core.MethodDefinition;
import ar.edu.uba.fi.jmit.aspecter.core.DefinitionFactory;
import ar.edu.uba.fi.jmit.aspecter.core.exception.AspecterRuntimeException;
import ar.edu.uba.fi.jmit.aspecter.core.interceptor.Interceptor;
import ar.edu.uba.fi.jmit.aspecter.core.interceptor.MethodAccessInterceptor;
import ar.edu.uba.fi.jmit.aspecter.core.interceptor.simple.SimpleMethodAccessInterceptor;
import ar.edu.uba.fi.jmit.aspecter.testUtils.ExceptionAfterMethodInterceptor;
import ar.edu.uba.fi.jmit.aspecter.testUtils.MethodAccessInterceptorSpy;

public class MethodAccessInterceptorTestCase extends AspecterTestCase {
	
	public void testSimpleInterceptor(){
		try {
			Interceptor interceptor = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getY");
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			ctClazz.instrument(interceptor);
			Class<?> clazz = ctClazz.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("getX").invoke(tp6);
			clazz.getMethod("setX", int.class).invoke(tp6, 1);
			clazz.getMethod("setY", int.class).invoke(tp6, 2);
			
			try{
				clazz.getMethod("getY").invoke(tp6);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testMultipleInterceptors(){
		try {
			ClassPool cp = new ClassPool(true);
			Interceptor interceptor1 = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getY");
			Interceptor interceptor2 = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getX");
			CtClass ctClazz = cp.getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			ctClazz.instrument(interceptor1);
			ctClazz.instrument(interceptor2);
			
			Class<?> clazz = ctClazz.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("setY", int.class).invoke(tp6, 0);
			clazz.getMethod("setX", int.class).invoke(tp6, 0);
			
			try{
				clazz.getMethod("getX").invoke(tp6);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				assertEquals("intercepted!", e.getCause().getMessage());
			}
			
			try{
				clazz.getMethod("getY").invoke(tp6);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				assertEquals("intercepted!", e.getCause().getMessage());
			}
			
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testNoInterception(){
		try {
			Interceptor interceptor = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest5", "getY");
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			ctClazz.instrument(interceptor);
			Class<?> clazz = ctClazz.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("setY", int.class).invoke(tp6, 0);
			clazz.getMethod("setX", int.class).invoke(tp6, 0);
			clazz.getMethod("getY").invoke(tp6);
			clazz.getMethod("getX").invoke(tp6);
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testNoInterception2(){
		try {
			Interceptor interceptor = new ExceptionAfterMethodInterceptor("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "someMethod");
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			ctClazz.instrument(interceptor);
			Class<?> clazz = ctClazz.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("setY", int.class).invoke(tp6, 0);
			clazz.getMethod("setX", int.class).invoke(tp6, 0);
			assertEquals(clazz.getMethod("getY").invoke(tp6), 0);
			assertEquals(clazz.getMethod("getX").invoke(tp6), 0);
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInterceptorWithJavassistParameter() {
		try {
			MethodDefinition methodDefinition = DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "setX");
			Interceptor interceptor = new MethodAccessInterceptor(methodDefinition) {
				
				@Override
				public boolean mustAspect(Expr expression) {
					return true;
				}
	
				@Override
				protected String aspectStatementAfter() {
					return "";
				}
	
				@Override
				protected String aspectStatementBefore() {
					return " $1 = $1*2; ";
				}
				
			};
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			ctClazz.instrument(interceptor);
			Class<?> clazz = ctClazz.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("setX", int.class).invoke(tp6, 5);//setY(5);
			assertEquals(10, clazz.getMethod("getX").invoke(tp6));
			clazz.getMethod("setY", int.class).invoke(tp6, 5);//setY(5);
			assertEquals(5, clazz.getMethod("getY").invoke(tp6)); //double!
		}catch(Exception e) {
			fail(e);
		}
	}
	
	public void testMultipleInterceptorsOnSameMethod() {
		try {
			SimpleMethodAccessInterceptor chaneValueInterceptor1 = new SimpleMethodAccessInterceptor(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "setX"));
			chaneValueInterceptor1.setAspectStatementBefore("$1=$1+1;");//x = x + 1
			SimpleMethodAccessInterceptor chaneValueInterceptor2 = new SimpleMethodAccessInterceptor(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "setX"));
			chaneValueInterceptor2.setAspectStatementBefore("$1=$1*100;");//x = x * 100
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			ctClazz.instrument(chaneValueInterceptor1);
			ctClazz.instrument(chaneValueInterceptor2);
			Class<?> clazz = ctClazz.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("setX", int.class).invoke(tp6, 1);//this should be intercepted twice
			assertEquals(200, clazz.getMethod("getX").invoke(tp6));
			
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testMultipleInterceptorsOnSameMethod2() {
		try {
			SimpleMethodAccessInterceptor chaneValueInterceptor1 = new SimpleMethodAccessInterceptor(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "setX"));
			chaneValueInterceptor1.setAspectStatementBefore("$1=$1+1;");//x = x + 1
			SimpleMethodAccessInterceptor chaneValueInterceptor2 = new SimpleMethodAccessInterceptor(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "getX"));
			chaneValueInterceptor2.setAspectStatementAfter("$_=$_*100;");//x = x * 100
			CtClass ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
			ctClazz.instrument(chaneValueInterceptor1);
			ctClazz.instrument(chaneValueInterceptor2);
			Class<?> clazz = ctClazz.toClass();
			Object tp6 = clazz.newInstance();
			clazz.getMethod("setX", int.class).invoke(tp6, 1);//this should be intercepted twice
			assertEquals(200, clazz.getMethod("getX").invoke(tp6));
			
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testCannotCompile() {
		SimpleMethodAccessInterceptor notCompilingIntereptor = null;
		try {
			notCompilingIntereptor = new SimpleMethodAccessInterceptor(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest4", "setX"));
			notCompilingIntereptor.setAspectStatementAfter(" not compiling code;");
		} catch (ClassNotFoundException e1) {
			fail(e1);
		}
		
		CtClass ctClazz = null;
		try {
			ctClazz = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", getNextClassName());
		} catch (NotFoundException e) {
			fail(e);
		}
		boolean catched = false;
		try {
			ctClazz.instrument(notCompilingIntereptor);
			fail("the added code must not compile!");
		} catch (CannotCompileException e) {
			catched = true;
		}
		assertTrue(catched);
	}
	
	public void testAddParameterTypes() {
		try {
			MethodAccessInterceptorSpy spy = new MethodAccessInterceptorSpy(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", "getX"));
			assertEquals(1, spy.getMethodsToIntercept().size());
			spy.addMethodToIntercept(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", "getX", new LinkedList<Class<?>>() , "int"));
			assertEquals(2, spy.getMethodsToIntercept().size());
			spy.addMethodToIntercept(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", "getX", new LinkedList<Class<?>>() , "long"));
			assertEquals(3, spy.getMethodsToIntercept().size());
			spy.addMethodToIntercept(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", "getX", new Class<?>[]{int.class} , "int"));
			assertEquals(4, spy.getMethodsToIntercept().size());
			try {
				spy.addMethodToIntercept(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest3", "getY"));
				fail("Exception should be thrown!");
			}catch(AspecterRuntimeException exception) {
				//expected
			}
			try {
				spy.addMethodToIntercept(DefinitionFactory.create("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest2", "getX"));
				fail("Exception should be thrown!");
			}catch(AspecterRuntimeException exception) {
				//expected
			}
			
		} catch (ClassNotFoundException e) {
			fail(e);
		}
	}
	
}
